Skip to content

chore: bump @metamask/device-mcp to ^0.6.0 - #69

Merged
cryptotavares merged 1 commit into
cryptotavares/mmcli-p1-15-compact-jsonfrom
cryptotavares/mmcli-p1-30-device-mcp-0-6
Oct 29, 2026
Merged

cryptotavares merged 1 commit into
cryptotavares/mmcli-p1-15-compact-jsonfrom
cryptotavares/mmcli-p1-30-device-mcp-0-6

Conversation

@cryptotavares

Copy link
Copy Markdown
Collaborator

Summary: Upgrade @metamask/device-mcp to ^0.6.0. 0.6.0 gives correct iOS trees (nested idb format) and fast, correct Android snapshots on busy screens (signed helper first). #44 and #67 were measured with these changes.

Stack context: mm CLI v2, Phase 1, P1-30. Base: #68, the top of the stack; review that first.

Problem

Core pins @metamask/device-mcp ^0.5.0 (#58). On 0.x a caret range does not pick up 0.6.0. Without 0.6.0:

  • iOS: device-mcp cannot read the nested idb ui describe-all format, so the element tree loses its hierarchy (device-mcp#38).
  • Android: snapshots start with uiautomator dump. On busy screens this waits about 11 s for an idle state, then fails with "could not get idle state" (device-mcp#39).

#44 (flat mobile tree) and #67 (mobile snapshot filtering) were measured against yalc builds of those two device-mcp PRs.

Solution

Adopt the device-mcp 0.6.0 release through the dependency range and the lockfile. Add one README sentence about the new Android helper install.

flowchart TD
    A[Mobile describe / observation] --> B[Core MobilePlatformDriver]
    B --> C{device-mcp 0.6.0 snapshot}
    C -->|iOS| D[idb describe-all, nested format read]
    C -->|Android default helper-first| E[Signed helper APK, no idle wait]
    E -->|non-trust failure| F[1 uiautomator dump attempt]
    D --> G[Core flat tree, refs, testIds]
    E --> G
    F --> G

    classDef new fill:#fff3cd,stroke:#d39e00
    class D,E,F new
Loading

Yellow boxes are new in 0.6.0.

What changes

Boundary Before After
package.json dependency range @metamask/device-mcp ^0.5.0 ^0.6.0
yarn.lock resolution npm 0.5.0 npm 0.6.0 (dependencies unchanged)
Android snapshot on the consumer's ADB backend uiautomator dump first Signed test-only helper first, installed on first use. DEVICE_MCP_ADB_SNAPSHOT=dump restores the old path
README.md No mention of the helper One sentence: helper install, first snapshot slower, how to opt out

Decisions for reviewers

Decision Reason
New PR on top of the stack instead of amending #58 Amending #58 would rebase the whole stack again. 0.6.0 was not released when the stack was built.
chore:, not breaking Every symbol core imports from device-mcp is unchanged. SnapshotResult only gains optional fields (source, captureMs), and core reads only hierarchy. The new Android default is a dependency runtime change on the consumer's backend, not a change to core's API.
Core does not set snapshotMode The consumer creates the DeviceBackend (Mobile uses AdbBackend directly). Core only documents the default and the opt-out.

Review guide

# File What to check
1 package.json The production dependency range is ^0.6.0.
2 yarn.lock Only the device-mcp entry and the workspace range change.
3 README.md The helper sentence in the mobile snapshot paragraph.
Testing

Unit and lint (at afaf75c, device-mcp 0.6.0 from npm, no yalc): yarn build OK, yarn test 51 files / 1793 tests pass, yarn lint passes (including the dedupe and constraints checks).

Device proof: Mobile with this branch's core (yalc) and device-mcp 0.6.0 from npm (no yalc device-mcp). Real-funds wallets; only read actions, tab bar Home/Explore, account list open/back and settings list (no row opened).

Check Extension iOS Android
Smoke: launch, unlock, describe, list-testids, get-state, cdp, screenshot PASS PASS PASS
A timed-out click does not block the next call; click by ref after it n/a PASS PASS
Two describes give identical refs n/a 67 / 67 242 / 242
Unchanged screen diff n/a +0 −0 +0 −0
Ref from the latest describe clicks the intended element n/a PASS PASS

Node counts are identical to the yalc device-mcp measurements used for #44 (measure-tree.sh):

Screen iOS nodes Android nodes
Wallet home 67 → 67 242 → 242
Account list 35 → 35 33 → 33
Settings 26 → 26 65 → 65

Android helper path: the emulator had helper 0.5.0. On the first snapshot, device-mcp 0.6.0 installed its bundled, signed 0.6.0 helper, and the signer check passed. During the smoke run, logcat shows 106 helper runs and 0 uiautomator runs.

Known and unchanged: on Android, 3 of 4 navigation observations still show the previous screen (same as before this bump). That is tracked separately.

0.6.0 adds:
- iOS: read the nested `idb ui describe-all` format, so the element tree
  keeps its hierarchy (MetaMask/device-mcp#38).
- Android: the default snapshot mode is now `helper-first`. The ADB backend
  installs a signed test-only helper APK on the device on first use
  (`adb install -t`, then a signer check) and captures without waiting for
  idle, with `uiautomator dump` as the fallback. This is faster and correct
  on busy screens, where `uiautomator dump` times out or returns "could not
  get idle state" (MetaMask/device-mcp#39). `DEVICE_MCP_ADB_SNAPSHOT=dump`
  keeps the old behaviour.

The flat mobile tree (`feat!: flat mobile a11y tree without layout-only
nodes`) and the mobile snapshot changes (`feat: filter mobile status/nav bars
and dev overlay`) were measured against these device-mcp changes. Core does
not use the new `snapshotMode`, `source` or `captureMs` fields. The README
notes the helper install and the opt-out.
@cryptotavares
cryptotavares added this pull request to stack #63 October 28, 2026 13:24
@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Updated@​metamask/​device-mcp@​0.5.0 ⏵ 0.6.074 -1410010097 +180

View full report

@cryptotavares
cryptotavares marked this pull request as ready for review October 28, 2026 13:37
@cryptotavares
cryptotavares requested a review from a team as a code owner October 28, 2026 13:38
@cryptotavares
cryptotavares merged commit d16c2d5 into main Oct 29, 2026
27 checks passed
@cryptotavares
cryptotavares deleted the cryptotavares/mmcli-p1-30-device-mcp-0-6 branch October 29, 2026 13:52
cryptotavares added a commit that referenced this pull request Oct 29, 2026
…TimeoutMs cap (#71)

> **Summary:** Fix the review comments on #62, #64 and #65 in one PR on
top of the stack. The approved PRs stay unchanged.

**Stack context:** mm CLI v2, Phase 1, **P1-43**. Base:
[#69](#69), the top of
the stack.

## Problem

| Comment | Problem |
|---|---|
| [#65: off-screen
animations](#65 (comment))
| The browser settle ignored animations whose target was outside the
viewport or had zero size. A drawer sliding in, a bottom sheet or an
accordion opening from height 0 start that way, so the observation
caught them mid-move. |
| [#64: late
click](#64 (comment))
| `click` / `type` checked the request signal only before the target
wait. When the request timed out during the wait, the agent got "timed
out", but the click still ran once the target appeared. |
| [#64:
batchTimeoutMs](#64 (comment))
| `batchTimeoutMs` accepted up to 300 s, but the queue and the CLI stop
`run_steps` after 30 s. |
| [#62: refs after
`none`](#62 (comment))
| `--observe none` does not refresh refs; some refs can then click a
different element. Not documented. |
| [#62: reads and
animations](#62 (comment))
| Reads do not wait for animations, even with `--observe full`. Not
documented. |

## Solution

```mermaid
flowchart TD
    A[click / type] --> B[Wait for target]
    B --> C{Request timed out?}
    C -->|yes| D[MM_REQUEST_ABORTED, no action]
    C -->|no| E[Click / tap / type]
    E --> F[Mutating observation]
    F --> G[Wait for every running finite animation, max 1 s]
    G --> H[Read tree]

    classDef new fill:#fff3cd,stroke:#d39e00
    class C,D,G new
```

Yellow boxes are new or changed.

## What changes

| Commit | Change |
|---|---|
| `fix: wait for animations that start off screen or at zero size` |
`animationsSettled` waits for every running, finite animation of an
element. Infinite animations are still ignored; the 1 s cap is
unchanged. |
| `fix: do not click or type after the request timed out` |
`IPlatformDriver.click` / `type` take an optional `signal`. Both drivers
call `signal.throwIfAborted()` after the target wait, right before the
action (mobile `type` also after the focus tap). The tools pass
`context.signal` and return `MM_REQUEST_ABORTED`. |
| `fix!: cap run_steps batchTimeoutMs at 30 s` | Schema range
1000–30000, like the `cdp` `timeoutMs`. |
| `docs: …` | README, SKILL.md and CLI help: refs after `--observe
none`, reads and animations, abort limits, `batchTimeoutMs`. |

## Decisions for reviewers

| Decision | Reason |
|---|---|
| One new PR instead of changing #62/#64/#65 | Pushing to an approved PR
dismisses its approval, and the restack would change every PR above it.
|
| An action that already started is not cancelled |
`@metamask/device-mcp` takes no signal (`waitForElement`, `tapElement`,
`typeText`), and Playwright's actionability checks are part of
`locator.click`. The driver checks the signal between steps. Cancelling
inside device-mcp needs a device-mcp change (follow-up). |
| Cap `batchTimeoutMs` at 30 s instead of raising the request budget for
batches | Matches the CLI and queue default; long flows can be split
into several batches. |
| Reads stay without the animation wait (docs only) | It is a less exact
snapshot, not a wrong action. Can be revisited. |
| The 5 s testId collection cap stays | Agreed with the reviewer: watch
real usage first. |

## Breaking changes

`run_steps` rejects `batchTimeoutMs` above 30000 (validation error),
also when a server raised `requestTimeoutMs`.

## Review guide

| # | File | What to check |
|---|---|---|
| 1 | `src/tools/utils/in-page.ts` | `animationsSettled` without the
viewport/size filter. |
| 2 | `src/platform/playwright-driver.ts`,
`src/platform/mobile-platform-driver.ts`, `src/platform/types.ts` |
`throwIfAborted()` placement. |
| 3 | `src/tools/interaction.ts` | Signal passed; abort mapped to
`MM_REQUEST_ABORTED`. |
| 4 | `src/validation/schemas.ts` | `batchTimeoutMs` range and
description. |
| 5 | `README.md`, `SKILL.md`, `src/cli/mm.ts` | Docs. |

<details>
<summary>Testing</summary>

`yarn test`: 51 files, 1809 tests pass. `yarn lint` passes. Each commit
passes the tests on its own.

**Extension** (e2e fixture wallet, headless), before = #69, after = this
branch:

| Check | Before | After |
|---|---|---|
| Drawer sliding in from off screen (400 ms): observationMs | 515–516
(did not wait) | 914–929 (waited) |
| Accordion from height 0 (400 ms): observationMs | 515–516 | 916–917 |
| Home click observationMs | 513–514 | 512–515 |
| Send / account list click observationMs | 12–183 / 14–164 | 14–212 /
15–164 |
| `run_steps` click whose target appears 1 s after the 30 s queue
timeout | **clicked** | **not clicked** |

**Android** (device-mcp 0.6.0, real funds, benign taps): all smoke and
ref checks pass, same as #69.

**iOS** (device-mcp 0.6.0, real funds, benign taps): all smoke and ref
checks pass, same as #69. (A first attempt hit the stale idb companion
after a simulator reboot, `dtuhidd connection ... invalidated`; fixed by
restarting the companion. See MetaMask/device-mcp#43 and #70.)

</details>
cryptotavares added a commit that referenced this pull request Oct 29, 2026
> **Summary:** Upgrade `@metamask/device-mcp` to `^0.7.0`. 0.7.0
declares `DeviceBackend.dispose()`, so the mobile driver calls it
without a type cast. iOS sessions get private IDB companion teardown and
recovery.

## Problem

Core pins `@metamask/device-mcp` `^0.6.0`. On 0.x a caret range does not
pick up 0.7.0.

- The `DeviceBackend` type in 0.6.0 has no `dispose()`. #70 therefore
calls the hook through a structural cast in
`MobilePlatformDriver.dispose()`.
- The README says core "does not update the core dependency range" and
that older device-mcp versions stay compatible. That text was correct
before 0.7.0 was released.

## Solution

Adopt the device-mcp 0.7.0 release. Remove the cast and the temporary
README text.

0.7.0 contains:

- [device-mcp#43](MetaMask/device-mcp#43):
optional `DeviceBackend.dispose()` and `reconnect()`, one private IDB
companion per iOS backend, and recovery when the companion or the
simulator HID connection is lost.
- [device-mcp#44](MetaMask/device-mcp#44):
correct recovery error reporting, and no resend of commands that can
change the device.
- [device-mcp#42](MetaMask/device-mcp#42):
Android clipboard helper.

```mermaid
flowchart TD
    A[mm cleanup / launch --force / stop] --> B[DriverLifecycle]
    B --> C[MobilePlatformDriver.dispose]
    C --> D{backend.dispose?}
    D -->|IdbBackend 0.7.0| E[Stop the private companion and recording]
    D -->|AdbBackend: no hook| F[Nothing to release]

    classDef new fill:#fff3cd,stroke:#d39e00
    class C,E new
```

Yellow boxes change: the driver calls the typed hook, and the 0.7.0 iOS
backend releases its companion.

## What changes

| Boundary | Before | After |
|---|---|---|
| `package.json` dependency range | `@metamask/device-mcp ^0.6.0` |
`^0.7.0` |
| `yarn.lock` resolution | npm 0.6.0 | npm 0.7.0 (dependencies
unchanged) |
| `MobilePlatformDriver.dispose()` | Casts the backend to `DeviceBackend
& { dispose?() }` | Calls `this.#backend.dispose?.()` on the typed
`DeviceBackend` |
| `README.md` teardown paragraph | Older device-mcp versions are
compatible; the range is not updated | Core requires 0.7.0 or later; iOS
releases its companion; Android has no hook |
| Disposal test name | "supports device-mcp versions without disposal" |
"completes for backends without a disposal hook" |

## Decisions for reviewers

| Decision | Reason |
|---|---|
| Keep the optional call `dispose?.()` | `dispose()` is still optional
in the 0.7.0 `DeviceBackend` type. `IdbBackend` implements it;
`AdbBackend` and `AppiumBackend` do not. |
| Keep the test for a backend without the hook | It now covers the ADB
backend, not an older device-mcp version. |
| Async IIFE instead of `Promise.resolve().then(...)` | Same behaviour,
simpler, and the same style as device-mcp. |
| `chore:`, not breaking | Core's own API does not change. Every symbol
core imports from device-mcp still exists. The `selectDevice()` break in
0.7.0 is on `LazyDeviceBackend`, which core does not use. |
| No changelog entry | Matches the previous bump (#69). |

## Review guide

| # | File | What to check |
|---|---|---|
| 1 | `package.json`, `yarn.lock` | The range is `^0.7.0`. Only the
device-mcp entry and the workspace range change. |
| 2 | `src/platform/mobile-platform-driver.ts` | The cast is gone.
Shared concurrent disposal and retry after failure are unchanged. |
| 3 | `README.md` | The teardown paragraph under "Session-scoped device
teardown". |
| 4 | `src/platform/mobile-platform-driver.dispose.test.ts` | Only the
test name changes. |

<details>
<summary>Testing</summary>

Device-mcp 0.7.0 is installed from npm, without yalc.

- `yarn build` passes.
- `yarn lint` passes, including ESLint, formatting, constraints,
dependency and dedupe checks, and changelog validation.
- `yarn test` passes: 53 files and 1,835 tests. Typechecking and
coverage thresholds pass. Thresholds are unchanged.
- `tsc --noEmit -p tsconfig.test.json` reports 8 errors on `main` and 8
on this branch. All 8 already exist on `main`.
- In the installed package, `dist/backends/types.d.mts` declares
`dispose?(): Promise<void>` and `reconnect?(): Promise<void>` on
`DeviceBackend`. `IdbBackend` declares `dispose()`; `AdbBackend` and
`AppiumBackend` do not.

No device run for this PR. The live teardown and recovery checks are in
device-mcp#43 and #70, and they used the same code.

</details>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants